<template>
  <div style="width: 96%;">
    <!-- validate-on-rule-change 设为 false, 以实现只在 blur 时验证 -->
    <el-form v-if="data" :validate-on-rule-change="false" ref="formRef" :model="data" :rules="rules" v-bind="$attrs" area-autocomplete label-width="100px">
      <el-scrollbar max-height="600px">
        <template v-for="(item, index) in options" :key="index">
          <!-- !item.children 没有子组件的情况 -->
          <el-form-item :prop="item.prop" :label="item.label" v-if="!item.children || !item.children.length">
            <component v-bind="item.attrs" :is="`el-${item.type}`" v-model="data[item.prop]" v-if="item.type !== 'upload' && item.type !== 'editor'" />

            <!-- upload 单独处理 -->
            <div v-else-if="item.type === 'upload'" style="width: 100%;">
              <el-upload
                :ref="(el:any) => setUploadRef(item.prop, el)"
                :auto-upload="true"
                multiple
                :action="item.action!"
                :file-list="item.value"
                method="post"
                :on-change="(file, files) => handleChange(item.prop, file, files)"
                :on-success="(resp, file, files) => handleSuccess(item.prop, resp, file, files)"
              >
                <!-- 按钮:选择文件 -->
                <template #trigger><el-button size="small" type="primary">选择文件</el-button></template>
                <!-- <el-button style="margin-left: 10px;" size="small" type="success" @click="">upload to server</el-button> -->
                <!-- <template #tip><div class="el-upload__tip">jpg/png files with a size less than 500kb</div></template> -->
              </el-upload>
            </div>

            <!-- 富文本组件 :id 为后面 Editor 准备-->
            <div v-if="item.type === 'editor'" :id="item.prop" style="width: 98%" />
          </el-form-item>

          <!-- 有子组件的情况 -->
          <el-form-item :prop="item.prop" :label="item.label" v-else>
            <component v-bind="item.attrs" :is="`el-${item.type}`" v-model="data[item.prop!]">
              <!-- 子组件 -->
              <component :is="`el-${sub.type}`" :key="i" :label="sub.label" :value="sub.value" v-bind="sub.attrs" v-for="(sub, i) in item.children" />
            </component>
          </el-form-item>
        </template>
      </el-scrollbar>
      <!-- 提交与重置 -->
      <div class="footer">
        <el-form-item>
          <slot name="footer" :form="formRef" :data="data"></slot>
        </el-form-item>
      </div>
    </el-form>
  </div>
</template>

<script setup lang="ts">
import { nextTick, onMounted, PropType, ref, watch } from 'vue'
import { FormOptions } from './types'
import E from 'wangeditor'

// 深拷贝,避免与父组件冲突
import cloneDeep from 'lodash/cloneDeep'
import { FormInstance, UploadFile, UploadInstance } from 'element-plus'

let props = defineProps({
  options: {
    type: Array as PropType<FormOptions[]>,
  },
})

// let model = ref<any>({}) 定义为 {} 时,对 checkbox-group 无效
// let rules = ref<any>({})
let data = ref<any>(null)
let rules = ref<any>(null)
let editors: Map<string, E> = new Map()

// 保存 upload 的绑定, 以解决多个 upload 时的重置问题
let uploadRefs: Map<string, UploadInstance> = new Map()
let setUploadRef = (prop: string, el: UploadInstance) => {
  if (el) {
    uploadRefs.set(prop, el)
  }
}

let intiForm = () => {
  // 不给组件赋值时报错
  if (props.options && props.options.length) {
    let m: any = {} // 赋值后形同: username='' password=''
    let r: any = {}
    props.options!.map((option, index) => {
      m[option.prop!] = option.value
      r[option.prop!] = option.rules
      // 初始化富组件
      // https://www.wangeditor.com/
      if (option.type === 'editor') {
        nextTick(() => {
          if (document.getElementById(option.prop)) {
            let editor = new E('#' + option.prop)
            editors.set(option.prop, editor) //

            // config 配置 要在 creat 之前
            if (option.placeholder) {
              editor.config.placeholder = option.placeholder!
            }
            editor.config.onchange = (newHtml: string) => {
              // option.value = newHtml // 配置中的 value 字段, 作为原始值保留
              data.value[option.prop] = newHtml // form 绑定的 data
            }
            editor.create()
            // 设置初始内容
            if (option.value) {
              editor.txt.html(option.value)
            }
          }
        })
      }
    })
    data.value = cloneDeep(m)
    rules.value = cloneDeep(r)
  }
}

onMounted(() => {
  intiForm()
})

// 如果是后续绑定,则执行此 watch
watch(
  () => props.options,
  () => {
    intiForm()
  },
  { deep: true },
)

// change 文件状态改变时的钩子，添加文件、上传成功和上传失败时都会被调用
// 在原有事件基础上增加 prop，以标识哪个组件的
const emits = defineEmits<{
  (e: 'on-upload-change', prop: string, uploadFile: UploadFile, uploadFiles: UploadFile[]): void
}>()

let handleChange = (prop: string, uploadFile: UploadFile, uploadFiles: UploadFile[]) => {
  // 清除验证
  // formRef.value?.clearValidate(prop)
  emits('on-upload-change', prop, uploadFile, uploadFiles)
}
// 成功上传
let handleSuccess = (prop: string, response: any, uploadFile: UploadFile, uploadFiles: UploadFile[]) => {
  data.value[prop] = { response, uploadFile, uploadFiles }
}

// form 提交
let formRef = ref<FormInstance | null>()
let resetForm = () => {
  formRef.value?.resetFields()
  // formRef.value?.validate() // 避免提交成功一次后,不再验证
  // 重置富文本
  props.options
    ?.filter(o => o.type === 'editor')
    .forEach(item => {
      let e = editors.get(item.prop)
      e!.txt.html(item.value) // 初始化时的内容
    })

  // 重置 upload
  uploadRefs.forEach((ur, prop) => {
    ur.clearFiles()
  })
}
defineExpose({
  resetForm,
})
</script>

<style scoped lang="scss">
.footer {
  display: flex;
  justify-content: flex-end;
  margin-right: 20px;
  padding-top: 4px;
  border-top: 1px solid #eee;
}
</style>
